package com.freewater.cargotracker.domain.model.handlingevent;

import com.freewater.cargotracker.domain.model.cargo.Cargo;
import com.freewater.cargotracker.domain.model.location.Location;
import com.freewater.cargotracker.domain.model.voyage.Voyage;
import com.github.meixuesong.aggregatepersistence.Versionable;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Objects;

public class HandlingEvent implements Serializable, Versionable {

    private static final long serialVersionUID = 1L;

    private Long id;
    private Voyage voyage;
    private Cargo cargo;
    private Type type;
    private Location location;
    private LocalDateTime registrationTime;
    private LocalDateTime completionTime;
    private int version;

    public static enum Type {

        // Loaded onto voyage from port location.
        LOAD(true),
        // Unloaded from voyage to port location
        UNLOAD(true),
        // Received by carrier
        RECEIVE(false),
        // Cargo claimed by recipient
        CLAIM(false),
        // Cargo went through customs
        CUSTOMS(false);

        private final boolean voyageRequired;

        Type(boolean voyageRequired) {
            this.voyageRequired = voyageRequired;
        }

        public boolean requiresVoyage() {
            return voyageRequired;
        }

        public boolean prohibitsVoyage() {
            return !requiresVoyage();
        }
    }

    public HandlingEvent(Cargo cargo,
                         Type type,
                         Location location,
                         LocalDateTime registrationTime,
                         LocalDateTime completionTime) {
        Validate.notNull(cargo, "Cargo is required");
        Validate.notNull(type, "Type is required");
        Validate.notNull(location, "Location is required");
        Validate.notNull(registrationTime, "Registration Time is required");
        Validate.notNull(completionTime, "Completion Time is required");

        if (type.requiresVoyage()) {
            throw new IllegalArgumentException("Voyage is required for event type " + type);
        }

        this.cargo = cargo;
        this.type = type;
        this.location = location;
        this.registrationTime = registrationTime;
        this.completionTime = completionTime;
    }

    public HandlingEvent(Cargo cargo,
                         Type type,
                         Location location,
                         LocalDateTime registrationTime,
                         LocalDateTime completionTime,
                         Voyage voyage) {
        Validate.notNull(cargo, "Cargo is required");
        Validate.notNull(type, "Type is required");
        Validate.notNull(location, "Location is required");
        Validate.notNull(registrationTime, "Registration Time is required");
        Validate.notNull(completionTime, "Completion Time is required");
        Validate.notNull(voyage, "Voyage is required");

        if (type.prohibitsVoyage()) {
            throw new IllegalArgumentException("Voyage is not allowed for event type " + type);
        }

        this.cargo = cargo;
        this.voyage = voyage;
        this.type = type;
        this.location = location;
        this.registrationTime = registrationTime;
        this.completionTime = completionTime;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        Validate.notNull(id, "id is required");
        this.id = id;
    }

    public Cargo getCargo() {
        return cargo;
    }

    public Voyage getVoyage() {
        return voyage;
    }

    public Type getEventType() {
        return type;
    }

    public Location getLocation() {
        return location;
    }

    public LocalDateTime getRegistrationTime() {
        return registrationTime;
    }

    public LocalDateTime getCompletionTime() {
        return completionTime;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public String getSummary() {
        StringBuilder builder = new StringBuilder(location.getName()).append("\n")
                .append(completionTime).append("\n")
                .append("Type: ").append(type).append("\n")
                .append("Reg.: ").append(registrationTime)
                .append("\n");

        if (voyage != null) {
            builder.append("Voyage: ").append(voyage.getVoyageNumber());
        }
        return builder.toString();
    }


    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || this.getClass() != obj.getClass()) {
            return false;
        }
        HandlingEvent other = (HandlingEvent) obj;
        return this.sameValueAs(other);
    }

    private boolean sameValueAs(HandlingEvent other) {
        return new EqualsBuilder()
                .append(other.cargo, this.cargo)
                .append(other.voyage, this.voyage)
                .append(other.type, this.type)
                .append(other.location, this.location)
                .append(other.completionTime, this.completionTime)
                .build();
    }

    @Override
    public int hashCode() {
        return Objects.hash(this.cargo,
                this.voyage,
                this.type,
                this.location,
                this.completionTime);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("\n--- Handling event ---\n")
                .append("Cargo: ").append(cargo.getTrackingId()).append("\n")
                .append("Type: ").append(type).append("\n")
                .append("Location: ").append(location.getName()).append("\n")
                .append("Completed on: ").append(completionTime).append("\n")
                .append("Registered on: ").append(registrationTime)
                .append("\n");

        if (voyage != null) {
            builder.append("Voyage: ").append(voyage.getVoyageNumber())
                    .append("\n");
        }

        return builder.toString();
    }
}
